<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>跟随鼠标绘制矩形框</title>
    <style>
        #canvas {
            border: 1px solid red;
        }
    </style>
</head>

<body>
    <canvas id="canvas" width="640" height="480"></canvas>
    <select name="mode" id="mode">
        <option value="point">标记关键点</option>
        <option value="rect">绘制矩形框</option>
    </select>
    <button id="undo" onclick="undo()">撤销</button>


    <script>
        const canvas = document.getElementById('canvas'),
            context = canvas.getContext('2d'),
            pointArray = [],
            history = []

        let dragging = false,
            mode = 'point',
            mousedown = null



        function Point(x, y, type) {
            this.x = x
            this.y = y
            this.type = type // 左击 1  右击 3
        }

        // 坐标转化为canvas坐标
        function windowToCanvas(x, y, type) {
            //返回元素的大小以及位置
            var bbox = canvas.getBoundingClientRect();
            // bbox 的宽度会加上 canvas 的 border 会影响精度
            return new Point(x - bbox.left * (canvas.width / bbox.width),
                y - bbox.top * (canvas.height / bbox.height), type)
        }

        function drawPoint(point) {
            context.save()
            context.fillStyle = point['type'] === 3 ? 'red' : 'green'
            context.beginPath();
            context.arc(point.x, point.y, 3, 0, Math.PI * 2, true)
            context.fill()
            context.font = "20px serif";
            context.fillText((pointArray.length).toString(), point.x - 5, point.y - 10)
            context.restore()

            pointArray.push(point)
        }

        function updateRect(point) {
            let w = Math.abs(point.x - mousedown.x)
            let h = Math.abs(point.y - mousedown.y)

            let left = point.x > mousedown.x ? mousedown.x : point.x
            let top = point.y > mousedown.y ? mousedown.y : point.y

            context.save();
            context.beginPath();
            context.rect(left, top, w, h);
            context.stroke();
            context.restore();
        }

        function showLastHistory() {
            context.putImageData(history[history.length - 1]['data'], 0, 0)
        }

        function undo() {
            if (history.length > 1) {
                history[history.length - 1]['mode'] === 'point' && pointArray.pop()
                history.pop()
                showLastHistory()
            }
        }

        function addHistoy(data) {
            history.push({
                mode,
                data: context.getImageData(0, 0, canvas.width, canvas.height)
            })
        }

        document.getElementById('mode').onchange = function(e) {
            mode = e.target.value
        }

        // 鼠标事件
        canvas.onmousedown = function(e) {
            e.preventDefault();
            mousedown = windowToCanvas(e.clientX, e.clientY, e.which)
            dragging = true
        }

        canvas.onmousemove = function(e) {
            e.preventDefault();
            if (dragging && mode === 'rect') { // 只有绘制矩形框时有效果
                showLastHistory() // 每次绘制先清除上一次
                updateRect(windowToCanvas(e.clientX, e.clientY, e.which))
            }
        }
        addHistoy() // 添加一张默认的数据
        canvas.onmouseup = function(e) {
            e.preventDefault();
            dragging = false
            mode === 'point' && drawPoint(mousedown)
            addHistoy() // 保存上一次数据

        }
        // 阻止页面的右击菜单栏
        canvas.oncontextmenu = function(e) {
            e.preventDefault()
        }
    </script>
</body>

</html>
